from google.appengine.ext import db
from google.appengine.api import memcache

import random
import operator
from itertools import imap as map

def _broken(*args, **kwargs):
    raise NotImplementedError

class _Totalized(tuple):
    pass

def tot(tot, prop):
    return _Totalized((tot, prop))


class ShardedPropertied(type(db.Model)):
    def __init__(self, name, bases, dict_):
        super(ShardedPropertied, self).__init__(name, bases, dict_)
        
        tots = []
        for key, value in dict_.iteritems():
            if isinstance(value, _Totalized):
                tot, prop = value
                tots.append((tot, key, operator.attrgetter(key)))
                setattr(self, key, prop)
        self._totalizers = tuple(tots)
                        

class ShardedModel(db.Model):
    __metaclass__ = ShardedPropertied
    
    class Config: #similar to class Meta:...
        pass
    
    _root_shard = db.Reference()
    
    def __init__(self,
                 parent=None,
                 key_name=None,
                 *args,
                 **kwds):
        
        if parent is not None:
            raise NotImplementedError #XX
        
        if key_name is None:
            raise db.BadArgumentError("key_name must be provided.")
        
        super(ShardedModel, self).__init__(parent,
                                           key_name,
                                           *args,
                                           **kwds)
            
    def _get_config(self):
        pass
    
    def _get_count_name(self):
        return (self.key().name() + '_count',
                self.__class__.__module__ + '.' + self.__class__.__name__)
        
    def _get_shard_name(self):
        #!! Check Math
        sampling_period, sampling_duration = self._get_config() #!!!
        name, namespace = self._get_count_name()
        
        if not random.randrange(0, sampling_period):
            writes = memcache.incr(name, namespace=namespace)
            if not writes:
                memcache.add(name, 1, namespace=namespace, time=sampling_duration)
                index = 1 #2
            else:
                index = writes #*2
        else:
            index = memcache.get(name, namespace=namespace) #*2
        
        #amplify index
        length = sampling_period // sampling_duration
        start = length * index
        index = random.randrange(start, start - length, -1) 
            
        #if index > max_shards:... #XX       
        
        if index > 1:
            shard_name = '%s%x' % (self.key().name(), index)
        else:
            shard_name = self.key().name()
        
        return shard_name
    
    def _get_all_shards(self):
        result = [self]
        result.extend(self.all().filter('_root_shard', self))
        return result
    
    def get_shard(self, **kwds):
        name = self._get_shard_name()
        ent = self.get_by_key_name(name)
        if ent is None:
            ent = self.__class__(key_name=name, _root_shard=self, **kwds)
        else:
            for totalizer, name, attrget in self._totalizers:
                v = kwds.get(name)
                if v is not None:
                    setattr(ent, name, totalizer((attrget(ent), kwds[name])))
        return ent
                
    def shrink(self, **kwargs):
        shards = self._get_all_shards()
        
        for totalizer, name, attrget in self._totalizers:
            setattr(self, name, totalizer(map(attrget, shards)))
            
        shards = iter(shards)
        for shard in shards:
            break
        for shard in shards:
            shard.delete() #rpc=?
            
        self.put(**kwargs)
    
    def get_total(self):
        shards = self._get_all_shards()
        result = self.__class__(**dict((name, totalizer(map(attrget, shards)))
            for totalizer, name, attrget  in self._totalizers))
        result.put = _broken
        return result
        
            
        
            
        